var entities = new Object();
entities.list = [];
entities.collisions = [];

// Update all entities
entities.update = function() {
    $enCanvas.clearCanvas();
    // Sprites need to be drawn starting at the bottom of the screen for proper overlap.
    var drawOrder = [];
    for (var i = 0; i < entities.list.length; i++) {
        var en = entities.list[i];
        var pos = en.getPos()[1];
        
        // Check if the entity needs to be deleted
        if (en.shouldDelete()) {
            var msg = en.getDeleteMsg();
            console.log("Removing entity #" + en.getId() + (msg ? " - '" + msg + "'" : ''));
            entities.list.remove(i);
            continue;
        }

        // Sort the list
        if (en.onScreen()) {
            var spliced = false;
            for (var n = 0; n < drawOrder.length; n++) {
                if (pos < drawOrder[n][0]) {
                    drawOrder.splice(n, 0, [pos, i]);
                    spliced = true;
                    break;
                }
            }
            if (!spliced) {
                drawOrder.push([pos, i]);
            }
        }

        // Update this entity
        en.update();
    }
    
    // Draw entities
    for (var i = 0; i < drawOrder.length; i++) {
        entities.list[drawOrder[i][1]].draw();
    }
}

entities.addCollision = function(x, y) {
    var i = map.iFromXY(x, y)
    if (typeof entities.collisions[i] == 'undefined')
        entities.collisions[i] = 0;
    entities.collisions[i]+=1
}

entities.remCollision = function(x, y) {
    var i = map.iFromXY(x, y)
    if (typeof entities.collisions[i] == 'undefined')
        entities.collisions[i] = 0;
    entities.collisions[i]-=1
}

entities.getDirArr = function(pos1, pos2) {
    return entities.getDir(pos1[0], pos1[1], pos2[0], pos2[1]);
}

entities.getDir = function(x, y, _x, _y) {
    if (x == _x && y == _y) {
        return -1;
    }
    return (x != _x ? (x > _x ? 1 : 3) : (y > _y ? 2 : 0));
}

var Entity = (function () {
    // Private static
    var nextId = 0;
    
    // Constructor
    var _class = function(sprite) {
        // Private
        var id = nextId++;
        var sprite = sprite;
        var anim = 'walk_right';
        var frame = 1; // Second frame is the idle frame
        var age = 0;
        
        var delSched = false;
        var delMsg = false;
        
        var facing = 0;
        var x = 0;
        var y = 0;
        
        var moving = false;
        var moving_to = [0, 0];
        var moving_frame = 0;
        var moving_type = 0; // 0 - walk, 1 - run, 2 - jump
        
        // Public
        this.getId = function() { return id; };
        this.getAge = function() { return age; };
        this.getPos = function() { return [x, y]; };
        this.getTruePos = function() {
            return [Math.round((map.tilewidth * x - 4) + moving_frame * (moving_to[0] - x) * map.tilewidth / 32),
                    Math.round((map.tilewidth * y - 7) + moving_frame * (moving_to[1] - y) * map.tilewidth / 32)];
        };
        this.getDest = function() { return moving_to; };
        this.isMoving = function() { return moving; };
        this.getSprite = function() { return sprite; };
        this.setSprite = function(nSprite) { sprite = nSprite; };
        this.setAnim = function(a) { anim = a };
        this.getAnim = function() { return anim; };
        this.getFrame = function() { return frame; };
        this.getMovingFrame = function() { return moving_frame; };
        this.onScreen = function() {
            var pos = this.getTruePos();
            if (pos[0] + sprites[sprite].width > map.camera.x
             && pos[1] + sprites[sprite].height > map.camera.y
             && pos[0] < map.camera.x + 480
             && pos[1] < map.camera.y + 270) {
                return true;
            }
            return false;
        };
        this.setPos = function(_x, _y) {
            entities.remCollision(x, y);
            x = _x; y = _y;
            entities.addCollision(x, y);
        };
        this.draw = function() {
            return this.drawAt(x, y);
        };
        this.drawAt = function(x, y) {
            if (typeof this.getSprite() !== 'undefined') {
                var pos = this.getTruePos();
                $enCanvas.drawSprite({
                    sprite: sprites[sprite],
                    anim: anim,
                    frame: frame,
                    x: pos[0] - map.camera.x,
                    y: pos[1] - map.camera.y
                });
            } else {
                this.scheduleDelete("ERROR: Invalid sprite");
            }
        };
        this.scheduleDelete = function(msg) { delSched = true; delMsg = msg };
        this.shouldDelete = function() { return delSched; };
        this.getDeleteMsg = function() { return delMsg; };
        this.aiRandomMovement = function(chance) {
            if (typeof chance !== 'number') { chance = .1; }
            if (!this.isMoving() && this.getAge() % 10 == 0 && Math.random() < chance) {
                var go_to = this.getPos();
                go_to[Math.round(Math.random() * 10 + this.getId()) % 2] += (Math.round(Math.random() * 10 + this.getId()) % 2 == 0 ? 1 : -1);
                
                this.moveTo(go_to[0], go_to[1]);
            }
        };
        this.canMoveTo = function(_x, _y) {
            if (moving
                 || (x - _x !== 0 && y - _y !== 0) 
                 || (x - _x == 0 && y - _y == 0) 
                 || Math.abs(x - _x) > 1 
                 || Math.abs(y - _y) > 1
                 || (_x > map.current.width - 1 
                 || _x < 0) 
                 || (_y > map.current.height - 1 || _y < 0)
                 || entities.collisions[map.iFromXY(_x, _y)] > 0
                 || map.current.collisions[map.iFromXY(_x, _y)] > 0) {
                return false;
            }
            return true;
        };
        this.moveTo = function(_x, _y) {
            // Validate moving
            if (!moving) {
                if (!this.canMoveTo(_x, _y)) {
                    if (x != _x || y != _y) {
                        /*
                        var newFace = entities.getDir(x, y, _x, _y);
                        if (newFace == facing) {
                            moving = true;
                            moving_to = [x, y];
                        }
                        */
                        this.face(entities.getDir(x, y, _x, _y));
                    }
                    return false;
                } else {
                    moving = true;
                    moving_to = [_x, _y];
                    return true;
                }
            }
            return false;
        };
        this.moveLeft = function() {
            return this.moveTo(x-1, y);
        }
        this.moveUp = function() {
            return this.moveTo(x, y-1);
        }
        this.moveRight = function() {
            return this.moveTo(x+1, y);
        }
        this.moveDown = function() {
            return this.moveTo(x, y+1);
        }
        this.move = function(dir) {
            var fn = [this.moveDown, this.moveLeft, this.moveUp, this.moveRight]
            return fn[dir].call(this);
        };
        this.getFace = function() { return facing; };
        this.face = function(dir) { facing = dir; };
        
        // Public update function
        this.update = function() {
            // Update core functions
            age++;
            
            if (moving && moving_frame == 0) {
                entities.remCollision(x, y);
                entities.addCollision(moving_to[0], moving_to[1]);
            }
            
            // Update movement
            var _anim = ["walk_down", "walk_left", "walk_up", "walk_right"];
            anim = _anim[facing];
            
            if (moving) {
                // Update facing based on movement
                if (!moving_to.compare(this.getPos())) {
                    facing = (x != moving_to[0] ? (x > moving_to[0] ? 1 : 3) : (y > moving_to[1] ? 2 : 0));
                }
                // End movement animation
                if (moving_frame++ == 31) {
                    moving = false;
                    moving_frame = 0;
                    x = moving_to[0];
                    y = moving_to[1];
                    frame = 1;
                } else {
                    // Update current frame
                    if (moving_frame % 8 == 0) {
                        frame = (frame + 1) % 4;
                    }
                }
            }
        };
        
        // Add to entities list and log
        entities.addCollision(x, y);
        entities.list.push(this);
        //console.log("Created entity #" + id);
    }
    
    return _class;
})();

var EntityNPC = (function () {
    var _class = function(_data) {        
        // Private
        var data = _data;
        var ai = data.behavior;
        
        // Call the superclass constructor
        this.constructor.super.call(this, data.sprite);
        this.setPos(data.x, data.y);
        this.face(data.facing);
        
        // New update function
        var _update = this.update;
        this.update = function() {
            _update.call(this);
            if (ai.type != "stationary") {
                this.aiRandomMovement();
            }
        }
        
        // Modify canMoveTo function
        var _canMoveTo = this.canMoveTo;
        this.canMoveTo = function(_x, _y) {
            if (_canMoveTo.call(this, _x, _y)) {
                if (ai.type == "wander") {
                    if (_x <= data.x + ai.radius
                     && _x >= data.x - ai.radius
                     && _y <= data.y + ai.radius
                     && _y >= data.y - ai.radius) {
                        return true;
                    }
                }
            }
            return false;
        }
    };
    // Inherit from Entity
    inherit(_class, Entity);
    
    return _class;
})();

var EntityTestAI = (function () {
    var _class = function(sprite) {
        // Call the superclass constructor
        this.constructor.super.call(this, sprite);
        
        // New update function
        var _update = this.update;
        this.update = function() {
            _update.call(this);
            this.aiRandomMovement();
        }
    };
    // Inherit from Entity
    inherit(_class, Entity);
    
    return _class;
})();

var EntityPlayer = (function () {
    var _class = function(sprite) {
        // Call the superclass constructor
        this.constructor.super.call(this, sprite);
        
        // New private variables
        var moveQueue = [-1, -1];
        
        // New update function
        var _update = this.update;
        this.update = function() {
            _update.call(this);
            if (!this.isMoving() && moveQueue[0] !== -1) {
                var dir = entities.getDirArr(this.getPos(), moveQueue);
                if (this.getFace() != dir || (this.getFace() == dir && keyboard.special.move[dir])) {
                    this.moveTo(moveQueue[0], moveQueue[1]);
                }
                moveQueue = [-1, -1];
            }
        }
        
        this.onScreen = function() {
            return true;
        };
        
        var _drawAt = this.drawAt;
        this.drawAt = function(x, y) {
            if (typeof this.getSprite() !== 'undefined') {
                var pos = this.getTruePos();
                if (map.camera.x == 0 || map.camera.x + 480 == map.tilewidth * map.current.width) {
                    x = pos[0] - map.camera.x;
                } else {
                    x = 232;
                }
                if (map.camera.y == 0 || map.camera.y + 270 == map.tilewidth * map.current.height) {
                    y = pos[1] - map.camera.y;
                } else {
                    y = 127;
                }
                $enCanvas.drawSprite({
                    sprite: sprites[sprite],
                    anim: this.getAnim(),
                    frame: this.getFrame(),
                    x: x,
                    y: y
                });
            } else {
                this.scheduleDelete("ERROR: Invalid sprite");
            }
        };

        var _moveTo = this.moveTo;
        this.moveTo = this.moveTo = function(_x, _y) {
            var ret = _moveTo.call(this, _x, _y);
            if (!ret) {
                this.queueMovement(_x, _y);
            }
            return ret;
        }
        
        this.queueMovement = function(_x, _y) {
            var dest = this.getDest();
            var pos = this.getPos();
            moveQueue = [_x + (pos[0] != dest[0] ? (pos[0] > dest[0] ? -1 : 1) : 0),
                         _y + (pos[1] != dest[1] ? (pos[1] > dest[1] ? -1 : 1) : 0)];
        }
    };
    // Inherit from Entity
    inherit(_class, Entity);
    
    return _class;
})();

// Array Remove - By John Resig (MIT Licensed)
Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

function inherit(cls, superCls) {
    // We use an intermediary empty constructor to create an
    // inheritance chain, because using the super class' constructor
    // might have side effects.
    var construct = function () {};
    construct.prototype = superCls.prototype;
    cls.prototype = new construct;
    cls.prototype.constructor = cls;
    cls.super = superCls;
}

function newFilledArray(len, val) {
    var a, rem, currlen;

    if (len == 0) {
        return [];
    }
    a = [val];
    currlen = 1;
    while (currlen < len) {
        rem = len - currlen;
        if (rem < currlen) {
            a = a.concat(a.slice(0, rem));
        }
        else {
            a = a.concat(a);
        }
        currlen = a.length;
    }
    return a;
}

// http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript
Array.prototype.compare = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time
    if (this.length != array.length)
        return false;

    for (var i = 0; i < this.length; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].compare(array[i]))
                return false;
        }
        else if (this[i] != array[i]) {
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;
        }
    }
    return true;
}